package pers.mihao.quickstream.common.util.scan;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import pers.mihao.quickstream.common.util.scan.filter.ScanClassFilter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 扫描class
 *
 * @author mihao
 * @since 2022/7/19
 */
public class ClassPathClassFileScanner {

    private static ResourcePatternResolver resourcePatternResolver;
    private final PackageResolve packageResolve;
    private final String resourcePattern;
    private static MetadataReaderFactory metadataReaderFactory;

    List<ScanClassFilter> filterList = new ArrayList<>();


    public ClassPathClassFileScanner(PackageResolve packageResolve) {
        this.packageResolve = packageResolve;
        this.resourcePattern = "**/*.class";
    }


    private ResourcePatternResolver getResourcePatternResolver() {
        if (resourcePatternResolver == null) {
            resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return resourcePatternResolver;
    }

    public Set<Class<?>> scanClass(String... basePackages) {
        ClassLoader loader = this.getClass().getClassLoader();
        Set<Class<?>> set = new HashSet<>();
        for (String basePackage : basePackages) {
            try {
                Resource[] resources = getResources(basePackage);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        String classname = getClassnameByResource(resource);
                        Class<?> clazz;
                        if ((clazz = filter(loader.loadClass(classname))) != null) {
                            set.add(clazz);
                        }
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return set;
    }

    protected Resource[] getResources(String basePackage) throws IOException {
        String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
        return resources;
    }


    protected String getClassnameByResource(Resource resource) throws IOException {
        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
        return metadataReader.getClassMetadata().getClassName();
    }


    public final MetadataReaderFactory getMetadataReaderFactory() {
        if (metadataReaderFactory == null) {
            metadataReaderFactory = new CachingMetadataReaderFactory();
        }
        return metadataReaderFactory;
    }

    private Class<?> filter(Class<?> loadClass) {
        for (ScanClassFilter filter : filterList) {
            if (filter.isNotMatch(loadClass)) {
                return null;
            }
        }
        return loadClass;
    }

    protected String resolveBasePackage(String basePackage) {
        return packageResolve.resolvePackage(basePackage);
    }


    public void addIncludeFilter(ScanClassFilter filter) {
        filterList.add(filter);
    }
}